define(
    [
        'jquery', 'underscore', 'edx-ui-toolkit/js/utils/html-utils', 'js/views/video/transcripts/utils',
        'js/views/abstract_editor', 'common/js/components/utils/view_utils', 'js/models/uploads', 'js/views/uploads'
    ],
    function($, _, HtmlUtils, TranscriptUtils, AbstractEditor, ViewUtils, FileUpload, UploadDialog) {
        'use strict';

        var VideoUploadDialog = UploadDialog.extend({
            error: function() {
                this.model.set({
                    uploading: false,
                    uploadedBytes: 0,
                    title: gettext('Sorry, there was an error parsing the subtitles that you uploaded. Please check the format and try again.')
                });
            }
        });

        var Translations = AbstractEditor.extend({
            events: {
                'click .create-setting': 'addEntry',
                'click .remove-setting': 'removeEntry',
                'click .upload-setting': 'upload',
                'change select': 'onChangeHandler'
            },

            templateName: 'metadata-translations-entry',
            templateItemName: 'metadata-translations-item',

            validFileFormats: ['srt'],

            initialize: function() {
                var templateName = _.result(this, 'templateItemName'),
                    tpl = document.getElementById(templateName).text,
                    languageMap = {};

                if (!tpl) {
                    console.error("Couldn't load template for item: " + templateName);
                }

                this.templateItem = _.template(tpl);

                // Initialize language map. This maps original language to the newly selected language.
                // Keys in this map represent language codes present on server, they don't change when
                // user selects a language while values represent currently selected language.
                // Initially, the map will look like {'ar': 'ar', 'zh': 'zh'} i.e {'original_lang': 'original_lang'}
                // and corresponding dropdowns will show language names Arabic and Chinese. If user changes
                // Chinese to Russian then map will become {'ar': 'ar', 'zh': 'ru'} i.e {'original_lang': 'new_lang'}
                _.each(this.model.getDisplayValue(), function(value, lang) {
                    languageMap[lang] = lang;
                });
                TranscriptUtils.Storage.set('languageMap', languageMap);
                AbstractEditor.prototype.initialize.apply(this, arguments);
            },

            getDropdown: (function() {
                var dropdown,
                    disableOptions = function(element, values) {
                        // eslint-disable-next-line no-shadow
                        var dropdown = $(element).clone();

                        _.each(values, function(value, key) {
                        // Note: IE may raise an exception if key is an empty string,
                        // while other browsers return null as excepted. So coerce it
                        // into null for browser consistency.
                            if (key === '') {
                                key = null;
                            }

                            var option = dropdown[0].options.namedItem(key);

                            if (option) {
                                option.disabled = true;
                            }
                        });

                        return dropdown;
                    };

                return function(values) {
                    if (dropdown) {
                        return disableOptions(dropdown, values);
                    }

                    dropdown = document.createElement('select');
                    dropdown.options.add(new Option());
                    _.each(this.model.get('languages'), function(lang, index) {
                        var option = new Option();

                        option.setAttribute('name', lang.code);
                        option.value = lang.code;
                        option.text = lang.label;
                        dropdown.options.add(option);
                    });

                    return disableOptions(dropdown, values);
                };
            }()),

            getValueFromEditor: function() {
                var dict = {},
                    items = this.$el.find('ol').find('.list-settings-item');

                _.each(items, function(element, index) {
                    var key = $(element).find('select option:selected').val(),
                        value = $(element).find('.input').val();

                    // Keys should be unique, so if our keys are duplicated and
                    // second key is empty or key and value are empty just do
                    // nothing. Otherwise, it'll be overwritten by the new value.
                    if (value === '') {
                        if (key === '' || key in dict) {
                            return false;
                        }
                    }

                    dict[key] = value;
                });

                return dict;
            },

            // @TODO: Use backbone render patterns.
            setValueInEditor: function(values) {
                var self = this,
                    frag = document.createDocumentFragment(),
                    dropdown = self.getDropdown(values),
                    languageMap = TranscriptUtils.Storage.get('languageMap');

                _.each(values, function(value, newLang) {
                    var $html = $(self.templateItem({
                        newLang: newLang,
                        originalLang: _.findKey(languageMap, function(lang) { return lang === newLang; }) || '',
                        value: value,
                        url: self.model.get('urlRoot')
                    }));
                    HtmlUtils.prepend($html, HtmlUtils.HTML(dropdown.clone().val(newLang)));
                    frag.appendChild($html[0]);
                });

                HtmlUtils.setHtml(
                    this.$el.find('ol'),
                    HtmlUtils.HTML([frag])
                );
            },

            addEntry: function(event) {
                event.preventDefault();
                // We don't call updateModel here since it's bound to the
                // change event
                this.setValueInEditor(this.getAllLanguageDropdownElementsData(true));
                this.$el.find('.create-setting').addClass('is-disabled').attr('aria-disabled', true);
            },

            removeEntry: function(event) {
                var self = this,
                    $currentListItemEl = $(event.currentTarget).parent(),
                    originalLang = $currentListItemEl.data('original-lang'),
                    selectedLang = $currentListItemEl.find('select option:selected').val(),
                    languageMap = TranscriptUtils.Storage.get('languageMap'),
                    edxVideoIdField = TranscriptUtils.getField(self.model.collection, 'edx_video_id');

                event.preventDefault();

                /*
            There is a scenario when a user adds an empty video translation item and
            removes it. In such cases, omitting will have no harm on the model
            values or languages map.
            */
                if (originalLang) {
                    ViewUtils.confirmThenRunOperation(
                        gettext('Are you sure you want to remove this transcript?'),
                        gettext('If you remove this transcript, the transcript will not be available for this component.'),
                        gettext('Remove Transcript'),
                        function() {
                            ViewUtils.runOperationShowingMessage(
                                gettext('Removing'),
                                function() {
                                    return $.ajax({
                                        url: self.model.get('urlRoot'),
                                        type: 'DELETE',
                                        data: JSON.stringify({lang: originalLang, edx_video_id: edxVideoIdField.getValue()})
                                    }).done(function() {
                                        self.setValueInEditor(self.getAllLanguageDropdownElementsData(false, selectedLang));
                                        TranscriptUtils.Storage.set('languageMap', _.omit(languageMap, originalLang));
                                    });
                                }
                            );
                        }
                    );
                } else {
                    this.setValueInEditor(this.getAllLanguageDropdownElementsData(false, selectedLang));
                }

                this.$el.find('.create-setting').removeClass('is-disabled').attr('aria-disabled', false);
            },

            upload: function(event) {
                var self = this,
                    $target = $(event.currentTarget),
                    $listItem = $target.parents('li.list-settings-item'),
                    originalLang = $listItem.data('original-lang'),
                    newLang = $listItem.find(':selected').val(),
                    edxVideoIdField = TranscriptUtils.getField(self.model.collection, 'edx_video_id'),
                    fileUploadModel,
                    uploadData,
                    videoUploadDialog;

                event.preventDefault();

                // That's the case when an author is
                // uploading a new transcript.
                if (!originalLang) {
                    originalLang = newLang;
                }

                // Transcript data payload
                uploadData = {
                    edx_video_id: edxVideoIdField.getValue(),
                    language_code: originalLang,
                    new_language_code: newLang
                };

                fileUploadModel = new FileUpload({
                    title: gettext('Upload translation'),
                    fileFormats: this.validFileFormats
                });

                videoUploadDialog = new VideoUploadDialog({
                    model: fileUploadModel,
                    url: this.model.get('urlRoot'),
                    parentElement: $target.closest('.xblock-editor'),
                    uploadData: uploadData,
                    onSuccess: function(response) {
                        var languageMap = TranscriptUtils.Storage.get('languageMap'),
                            newLangObject = {};

                        // new language entry to be added to languageMap
                        newLangObject[newLang] = newLang;

                        // Update edx-video-id
                        edxVideoIdField.setValue(response.edx_video_id);

                        // Update language map by omitting original lang and adding new lang
                        // if languageMap is empty then newLang will be added
                        // if an original lang is replaced with new lang then omit the original lang and the add new lang
                        languageMap = _.extend(_.omit(languageMap, originalLang), newLangObject);
                        TranscriptUtils.Storage.set('languageMap', languageMap);

                        // re-render the whole view
                        self.setValueInEditor(self.getAllLanguageDropdownElementsData());
                    }
                });
                videoUploadDialog.show();
            },

            enableAdd: function() {
                this.$el.find('.create-setting').removeClass('is-disabled').attr('aria-disabled', false);
            },

            onChangeHandler: function(event) {
                var $target = $(event.currentTarget),
                    $listItem = $target.parents('li.list-settings-item'),
                    originalLang = $listItem.data('original-lang'),
                    newLang = $listItem.find('select option:selected').val(),
                    languageMap = TranscriptUtils.Storage.get('languageMap');

                // To protect against any new/unsaved language code in the map.
                if (originalLang in languageMap) {
                    languageMap[originalLang] = newLang;
                    TranscriptUtils.Storage.set('languageMap', languageMap);

                    // an existing saved lang is changed, no need to re-render the whole view
                    return;
                }

                this.enableAdd();
                this.setValueInEditor(this.getAllLanguageDropdownElementsData());
            },

            /**
         * Constructs data extracted from each dropdown. This will be used to re-render the whole view.
         */
            getAllLanguageDropdownElementsData: function(isNew, omittedLanguage) {
                var data = {},
                    languageDropdownElements = this.$el.find('select'),
                    languageMap = TranscriptUtils.Storage.get('languageMap');

                // data object will mirror the languageMap. `data` will contain lang to lang map as explained below
                // {originalLang: originalLang};            original lang not changed
                // {newLang: originalLang};                 original lang changed to a new lang
                // {selectedLang: ''};                      new lang to be added, no entry in languageMap
                _.each(languageDropdownElements, function(languageDropdown) {
                    var language = $(languageDropdown).find(':selected').val();
                    data[language] = _.findKey(languageMap, function(lang) { return lang === language; }) || '';
                });

                // This is needed to render an empty item that
                // will be further used to upload a transcript.
                if (isNew) {
                    data[''] = '';
                }

                // This Omits a language from the dropdown's data. It is
                // needed when an item is going to be removed.
                if (typeof omittedLanguage !== 'undefined') {
                    data = _.omit(data, omittedLanguage);
                }

                return data;
            }
        });

        return Translations;
    });
